#ifndef ODBC_DETAILS_PARAMETER_BUFFER_HPP
#define ODBC_DETAILS_PARAMETER_BUFFER_HPP

#include "odbc/details/bindable_buffer.hpp"
#include "odbc/odbc_error.hpp"
#include <algorithm>

namespace odbc
{
namespace details
{

///	\ingroup OdbcLibrary
/// \internal
///	\brief
///		It provides the specific functionality for a statement parameter buffer.
template< typename T, typename Tr = type_traits< T> >
class parameter_buffer: public bindable_buffer< T, Tr>
{
	typedef bindable_buffer< T, Tr> MyBaseT_;
public:
	
	/// \brief
	///		It initializes the buffer. 
	/// \details
	///		The bind position is set to an invalid value. Initially, the buffer contains a null value.
	parameter_buffer( SQLSMALLINT direction)
		: direction_( direction)
	{ 
	}

	///	\brief
	///		It initializes the buffer.  
	/// \details
	///		The bind position is set to an invalid value. Initially, the buffer contains a null value.
	/// \param size the size of the buffer to be created. Since the default colummn buffer implementation refers to 
	///		fixed size types, this parameter is ignored.
	explicit parameter_buffer( size_type size, SQLSMALLINT direction)
		: direction_( direction),
		  MyBaseT_( size)
	{
	}

	/// \brief
	///		It binds the wrapped buffer to the specified statement at the specified position.\n
	///	\pre
	///		The buffer was not bound before.
	/// \pre
	///		The specified bind position is not \c -1.
	/// \param stat 
	///		the statement the buffer will be bound to
	/// \param bindPosition 
	///		the number of the column the buffer will be bound to. It should not be \c -1.
	/// \note
	///		Because there can't be interleaved bounded buffers and not bounded buffers (not all ODBC drivers support 
	///		that), there is no explicit binding done. All the data is retrieved through an explicit \c SQLGetData ODBC 
	///		API call.
	virtual void bind( statement& stat, SQLUSMALLINT bindPosition)
	{
		assert( bindPosition != SQLUSMALLINT( -1));
		SQLRETURN retCode = SQLBindParameter(
			bindable_buffer_base::statement_handle( stat),
			bindPosition, 
			direction_,
			(SQLSMALLINT)type_traits::odbc_type_id,
			(SQLSMALLINT)type_traits::sql_type_id,
			(SQLUINTEGER)type_traits::size_of_buffer( buffer_),
			0,
			(SQLPOINTER)type_traits::buffer_to_void( buffer_),
			(SQLINTEGER)type_traits::size_of_buffer( buffer_),
			&lengthIndicator_);
		odbc_error::check_success( retCode, stat);
		bindPosition_ = bindPosition;
	}

	///	\brief 
	///		It sets the value for the parameter.
	/// \details
	///		It converts the specified value to the format required by the buffer.
	/// \param val
	///		The value to be set for the parameter. If the value is empty, the parameter value is marked to be \c NULL.
	virtual void set_value( const boost::any& val)
	{
		if( val.empty())
		{
			// NULL value was passed as parameter. Mark the parameter value as 
			//	NULL.
			lengthIndicator_ = SQL_NULL_DATA;
		}
		else
		{
			value_type targetValue = boost::any_cast< value_type>( val);
			type_traits::convert( targetValue, buffer_);
			// Mark the length of the buffer. If the buffer type is fixed size, the length value will not be considered 
			//	(unless is \c SQL_NULL_DATA, which shouldn't be possible here). For variable size values, set the 
			//	minimum size between the buffer and the value (so, if the buffer is larger than the value, no "garbage" 
			//	data is entered in the buffer).
			lengthIndicator_ = (SQLINTEGER)std::min< type_traits::size_type>( 
				type_traits::size_of_value( targetValue),
				type_traits::size_of_buffer( buffer_));
		}
	}

private:
	///	It contains the direction of the parameter.
	SQLSMALLINT direction_;
};

} // namespace details
} // namespace odbc

#endif // ODBC_DETAILS_PARAMETER_BUFFER_HPP
